﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using MPI;
using System.Threading;
using System.Threading.Tasks;

namespace Dwarf.Managed
{
    /// <summary>
    /// Solve the Combinational Logic problem with Hybrid parallelization.
    /// </summary>
    /// <remarks> 
    /// For the demonstration purposes of the project, 
    /// we have chosen the algorithm that counts the number of “1” bits in a bit string.
    /// </remarks>
    class Solver
    {
        /// <summary>
        /// Reference to file.
        /// </summary>
        public BinaryReader input;

        /// <summary>
        /// Length of file
        /// </summary>
        public int length;

        /// <summary>
        /// Result counter
        /// </summary>
        public long resultCount = 0;

        /// <summary>
        /// Size of block for each process
        /// </summary>
        public int bufferSize = 0;

        /// <summary>
        /// Empty constructor.
        /// </summary>
        public Solver()
        {
        }

        /// <summary>
        /// Hybrid based method.
        /// </summary>
        public void Solve()
        {
            ulong[] buffer = new ulong[bufferSize]; //Working buffer.
            int len;                                //Lenght of block.
            long localCount = 0;                    //Counter.

            int bufSend = 0;                        //Number of sent blocks.
            int bufRecv = 0;                        //Number of recieved blocks.

            CompletedStatus status;                 //Status of receiving.

            byte fileOver = 0;                      //Flag of EOF.

            if (Communicator.world.Rank == Settings.ROOT_PROCESS)
            {
                //Send different blocks of data to each process
                for (int i = 1; i < Communicator.world.Size; i++)
                {
                    Communicator.world.Send(fileOver, i, i);

                    if (fileOver == 0)
                    {
                        len = Math.Min((int)(length - input.BaseStream.Position) / 8, bufferSize);

                        for (int j = 0; j < len; j++)
                        {
                            buffer[j] = input.ReadUInt64();
                        }

                        if (len < bufferSize)
                        {
                            ulong lastCount = 0;

                            int numBytes = (int)(length - input.BaseStream.Position);

                            for (byte j = 0; j < numBytes; j++)
                            {
                                lastCount += (ulong)(input.ReadByte() << j * 8);
                            }

                            buffer[len] = lastCount;

                            len++;

                            fileOver = 1;
                        }

                        Communicator.world.Send(len, i, i);
                        Communicator.world.Send(buffer, i, i);

                        bufSend++;
                    }
                }

                while (bufSend > bufRecv)                        //While not End Of File
                {
                    //Receive the resulting value
                    Communicator.world.Receive(Communicator.anySource, Communicator.anyTag, out localCount, out status);

                    bufRecv++;
                    resultCount += localCount;

                    Communicator.world.Send(fileOver, status.Source, status.Source);

                    //Send current block of data to released process 
                    if (fileOver == 0)
                    {
                        len = Math.Min((int)(length - input.BaseStream.Position) / 8, bufferSize);

                        for (int i = 0; i < len; i++)
                        {
                            buffer[i] = input.ReadUInt64();
                        }

                        if (len < bufferSize)
                        {
                            ulong lastCount = 0;

                            int numBytes = (int)(length - input.BaseStream.Position);

                            for (byte i = 0; i < numBytes; i++)
                            {
                                lastCount += (ulong)(input.ReadByte() << i * 8);
                            }

                            buffer[len] = lastCount;

                            len++;

                            fileOver = 1;
                        }

                        Communicator.world.Send(len, status.Source, status.Source);
                        Communicator.world.Send(buffer, status.Source, status.Source);

                        bufSend++;
                    }
                }
            }
            else
            {
                while (true)                                     //Infinite loop
                {
                    //Receive the flag of eof
                    Communicator.world.Receive(Settings.ROOT_PROCESS, Communicator.anyTag, out fileOver);

                    if (fileOver != 0) break;

                    //Receive block of data
                    Communicator.world.Receive(Settings.ROOT_PROCESS, Communicator.anyTag, out len);
                    Communicator.world.Receive(Settings.ROOT_PROCESS, Communicator.anyTag, ref buffer);

                    localCount = 0;
                    //Loop for length of block.
                    //since the iterations of the loop are independent by data, 
                    //each iteration may be performed as separate thread. 
                    Parallel.For(0, len, (i)=>
                    //for (int i = 0; i < len; i++)
                    {
                        ulong count = 0;

                        //This line of code is used for decrease of input file size.
		                //Such modification would reduce the input file size, 
		                //but the algorithm is still belongs dwarf.
		                //Also this line does not affect the result.
                        for (int k = 0; k < 1000000; k++)
                        {
                            count = buffer[i];

                            count -= (count >> 1) & 0x5555555555555555;
                            count = (count & 0x3333333333333333) + ((count >> 2) & 0x3333333333333333);
                            count = (count + (count >> 4)) & 0x0f0f0f0f0f0f0f0f;                            
                        }

                        Interlocked.Add(ref localCount, (long)(count * 0x0101010101010101 >> 56));
                    });

                    //Return resulting value to the root process
                    Communicator.world.Send(localCount, Settings.ROOT_PROCESS, MPI.Communicator.world.Rank);
                }
            }
        }
    }
}
